﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Security;
using Mindfor.Web.Config;
using Mindfor.Web.Data;
using Mindfor.Web.Models;
using Mindfor.Web.Modules.Users;
using Mindfor.Web.Routing;
using NHibernate;
using NHibernate.Linq;
using System.Net.Mail;

namespace Mindfor.Web.Modules.Users.Controllers
{
	/// <summary>
	/// Class allows manipulation current user.
	/// </summary>
	public class UserController : CmsController
	{
		[Widget("Info")]
		[ReturnUrlEnable]
		public virtual ActionResult InfoWidget()
		{
			return PartialView(User);
		}

		[Widget("Login")]
		[ReturnUrlEnable]
		public virtual ActionResult LoginWidget()
		{
			return PartialView(User);
		}

		/// <summary>
		/// Returns PageResult which renders login view.
		/// </summary>
		[HttpGet]
		[LoadModelState]
		[ReturnUrlEnable]
		public virtual ActionResult Login()
		{
			if (IsAuthenticated)
				return RedirectToReturnUrl(FormsAuthentication.DefaultUrl);
			return View();
		}

		/// <summary>
		/// Authenticates user.
		/// </summary>
		/// <param name="model">Model that contains login information.</param>
		/// <param name="errorUrl">Url to redirect to if error occured. If null then redirect to Login action.</param>
		[HttpPost]
		public virtual ActionResult Login(AuthenticateModel model, string returnUrl, string errorUrl)
		{
			// check authenticated
			if (IsAuthenticated)
				return RedirectToReturnUrl(FormsAuthentication.DefaultUrl);

			// if model is valid
			if (ModelState.IsValid)
			{
				// get user by login or e-mail
				User user;
				switch (CmsConfig.Default.User.AuthenticateBy)
				{
					case AuthenticationType.Login:
						user = Data.Query<User>().GetUser(model.Identity);
						break;
					default:
						user = Data.Query<User>().GetUserByEmail(model.Identity);
						break;
				}

				// validate user properties and pwd
				if (user == null)
					ModelState.AddModelError("", "Пользователь не найден");
				else if (!user.ValidatePassword(model.Pwd))
					ModelState.AddModelError("Pwd", "Неверный пароль");
				else if (!user.IsApproved)
					ModelState.AddModelError("", "Пользователь еще не активирован");
				else if (user.IsBanned)
					ModelState.AddModelError("", "Пользователь был заблокирован");

				// authenticate using form
				if (ModelState.IsValid)
				{
					FormsAuthentication.SetAuthCookie(user.Login, model.Remember);
					return RedirectToReturnUrl(FormsAuthentication.DefaultUrl);
				}
			}
						
			// something wrong
			this.SaveModelState();
			if (!String.IsNullOrEmpty(errorUrl))
				return Redirect(errorUrl);
			return RedirectToAction("Login", new { returnUrl });			
		}

		/// <summary>
		/// Logout current user.
		/// </summary>
		/// <param name="returnUrl">Url to return to. If <c>Null</c> then user will be redirected to the root page.</param>
		[HttpGet]
		[CmsAuthorize]
		[ReturnUrlEnable]
		[AdminLink("Выход", null, ShowManageToolbar = false)]
		public virtual ActionResult Logout()
		{
			// logout
			FormsAuthentication.SignOut();

			// redirect
			return RedirectToReturnUrl(FormsAuthentication.DefaultUrl);
		}

		/// <summary>
		/// Returns PageResult which renders register view.
		/// </summary>
		[HttpGet]
		[ReturnUrlEnable]
		[LoadModelState]
		public virtual ActionResult Register()
		{
			if (IsAuthenticated)
				return RedirectToReturnUrl(FormsAuthentication.DefaultUrl);
			return View();
		}

		/// <summary>
		/// Registers user.
		/// </summary>
		/// <param name="model">Registration model that contains user information.</param>
		/// <param name="returnUrl">Url to return to. If <c>Null</c> then user will be redirected to the root page.</param>
		/// <param name="errorUrl">Url to redirect to if error occured. If null then redirect to Login action.</param>
		[HttpPost]
		public virtual ActionResult Register(RegistrationModel model, string returnUrl, string errorUrl)
		{
			if (IsAuthenticated)
				return RedirectToReturnUrl(FormsAuthentication.DefaultUrl);

			if (ModelState.IsValid)
			{
				// validate user
				if (CmsConfig.Default.User.AuthenticateBy == AuthenticationType.Login &&
					Data.Query<User>().CheckLoginExist(model.Login))
					ModelState.AddModelError("login", "Пользователь с таким логином уже существует");
				if (CmsConfig.Default.User.IsEmailRequired && 
					Data.Query<User>().CheckEmailExist(model.Email))
					ModelState.AddModelError("login", "Пользователь с таким логином уже существует");

				if (ModelState.IsValid)
				{
					// create user
					User user = new User();
					if (CmsConfig.Default.User.AuthenticateBy == AuthenticationType.Email)
						user.Login = model.Email;
					user.Email = String.IsNullOrEmpty(model.Email) ? null : model.Email;
					user.ChangePassword(model.Pwd);
				
					// generate email
					Uri activationUrl = GenerateActivationKey(user);
					MailQueue mailItem = GenerateRegistrationEmail(user, model.Pwd, activationUrl);
					if (mailItem == null)
						user.IsApproved = true;
					
					// submit
					using (ITransaction t = Data.BeginTransaction())
					{
						Data.Save(user);
						if (mailItem != null)
							Data.Save(mailItem);
						t.Commit();
					}

					// return view or redirect
					RegisterSuccessful(user);
					ViewData.Model = user;
					if (user.IsApproved)
					{
						FormsAuthentication.SetAuthCookie(user.Login, false);
						if (!String.IsNullOrEmpty(returnUrl))
							return RedirectToReturnUrl(returnUrl);
					}
					return View("RegisterSuccessful");
				}
			}

			// if smth wrong
			this.SaveModelState();
			if (!String.IsNullOrEmpty(errorUrl))
				return Redirect(errorUrl);
			return RedirectToAction("Register", new { returnUrl });
		}

		/// <summary>
		/// Override this method to execute code when register successful.
		/// </summary>
		/// <param name="user">Registered user.</param>
		protected virtual void RegisterSuccessful(User user)
		{
		}

		/// <summary>
		/// Activates user by activation key.
		/// </summary>
		/// <param name="key">User activation key.</param>
		[HttpGet]
		public virtual ActionResult Activate(string key)
		{
			if (IsAuthenticated)
				return Redirect(FormsAuthentication.DefaultUrl);

			// check guid
			Guid guid = Guid.Empty;
			try
			{
				guid = new Guid(key);
			}
			catch { }

			// find user
			User user = Data.Query<User>().FirstOrDefault(u => u.ActivationGuid == guid && !u.IsApproved);
			if (user != null)
			{
				// activate
				user.IsApproved = true;
				user.ActivationGuid = null;
				using (ITransaction t = Data.BeginTransaction())
				{
					Data.Update(user);
					t.Commit();
				}

				// return view
				return View(new ActivateModel(true, user));
			}

			// something wrong
			return View(new ActivateModel(false, null));
		}

		/// <summary>
		/// Checks wheather login is free.
		/// </summary>
		/// <param name="login">Login to check.</param>
		/// <returns>True, if login is free. Otherwise False.</returns>
		[UrlRoute(Name="UserCheckLogin")]
		public virtual JsonResult CheckLogin(string login)
		{
			bool ok = !String.IsNullOrEmpty(login) && !Data.Query<User>().CheckLoginExist(login);
			return Json(ok, JsonRequestBehavior.AllowGet);
		}

		/// <summary>
		/// Checks wheather e-mail is free.
		/// </summary>
		/// <param name="email">E-mail to check.</param>
		/// <returns>True, if e-mail is free. Otherwise False.</returns>
		[UrlRoute(Name="UserCheckEmail")]
		public virtual JsonResult CheckEmail(string email)
		{
			bool ok = !String.IsNullOrEmpty(email) && !Data.Query<User>().CheckEmailExist(email);
			return Json(ok, JsonRequestBehavior.AllowGet);
		}

		/// <summary>
		/// Returns PageResult which renders change user info.
		/// </summary>
		[HttpGet]
		[CmsAuthorize]
		[LoadModelState]
		[ReturnUrlEnable]
		public virtual ActionResult Change()
		{
			// load model
			UserChangeModel model = new UserChangeModel()
			{
				Login = User.Login,
				Email = User.Email
			};
			ViewData.Model = model;
			return View();
		}

		/// <summary>
		/// Changes user info.
		/// </summary>
		[HttpPost]
		[CmsAuthorize]
		public virtual ActionResult Change(UserChangeModel model, string returnUrl)
		{
			if (!IsAuthenticated)
				return RedirectToAction("Login");

			// check model
			if (ModelState.IsValid)
			{
				string oldLogin = User.Login;

				// change login and e-mail
				if (!String.IsNullOrEmpty(model.Email))
					User.Email = model.Email.ToLower();

				if (CmsConfig.Default.User.AuthenticateBy == AuthenticationType.Login)
				{
					if (!String.IsNullOrEmpty(model.Login))
						User.Login = model.Login;
				}
				else if (CmsConfig.Default.User.AuthenticateBy == AuthenticationType.Email)
					User.Login = User.Email;
				
				// change pwd
				if (!String.IsNullOrEmpty(model.NewPwd))
				{
					if (User.ValidatePassword(model.Pwd))
						User.ChangePassword(model.NewPwd);
					else
						ModelState.AddModelError("Pwd", "Неверный текущий пароль");
				}

				// if everything ok
				if (ModelState.IsValid)
				{
					// submit changes
					using (ITransaction t = Data.BeginTransaction())
					{
						Data.Update(User);
						t.Commit();
					}

					// relogin user
					if (oldLogin != User.Login)
						FormsAuthentication.SetAuthCookie(User.Login, false);

					// show complete message
					if (String.IsNullOrEmpty(returnUrl))
						returnUrl = FormsAuthentication.DefaultUrl;
					return Message("Данные пользователя успешно изменены.", returnUrl);
				}
			}

			// something wrong
			this.SaveModelState();
			return RedirectToAction("Change", new { returnUrl });
		}

		/// <summary>
		/// Returns view to recover password.
		/// </summary>
		/// <param name="key">Password recovery key or null if key is requested.</param>
		[UrlRoute("RecoverPwd/{key}")]
		[UrlRouteDefault("key")]
		[HttpGet]
		[LoadModelState]
		public virtual ActionResult RecoverPwd(string key)
		{
			if (IsAuthenticated)
				return RedirectToReturnUrl(null);

			// if need to show form
			if (String.IsNullOrEmpty(key))
			{
				return View();
			}
			// if need to generate new pwd
			else
			{
				// check guid
				Guid guid = Guid.Empty;
				try
				{
					guid = new Guid(key);
				}
				catch { }

				// find user
				User user = Data.Query<User>().FirstOrDefault(u => u.PasswordRecoveryGuid == guid);
				if (user != null)
				{
					// change pwd
					string pwd = user.GeneratePassword();
					user.PasswordRecoveryGuid = null;
					using (ITransaction t = Data.BeginTransaction())
					{
						Data.Update(user);
						t.Commit();
					}

					// show message
					return Message(
						String.Format("Пароль для пользователя <b>{0}</b> был успешно изменен.<br/>Новый пароль: <b>{1}</b>", user.Login, pwd),
						new Link("Выполнить вход", Url.Action("Login"))
					);
				}

				// something wrong
				return Message("Неверный ключ восстановления пароля.");
			}
		}

		/// <summary>
		/// Checkes user e-mail and sends him message with url to recover password.
		/// </summary>
		/// <param name="model">Model that contains data to recover password.</param>
		[HttpPost]
		public virtual ActionResult RecoverPwd(RecoverPwdModel model)
		{
			// check
			if (IsAuthenticated)
				return RedirectToReturnUrl(null);

			// if valid
			if (ModelState.IsValid)
			{
				User user = Data.Query<User>().GetUserByEmail(model.Email);
				if (user == null)
					ModelState.AddModelError("Email", "Пользователь не найден");
				else
				{
					Guid guid = Guid.NewGuid();
					Uri recoverUrl = new Uri(SiteUrl, Url.Action("RecoverPwd", new { key = guid }));

					// set user data
					user.PasswordRecoveryGuid = guid;
					using (ITransaction t = Data.BeginTransaction())
					{
						Data.Update(user);
						t.Commit();
					}

					// send e-mail
					MailTemplate template = Data.Query<MailTemplate>().Get(UsersModule.MailTemplateRecoverPwd, Module.Name, Language);
					if (template == null)
					{
						LogError(new InvalidOperationException(String.Format("Cannot find mail template \"{0}\" to recover password.", UsersModule.MailTemplateRecoverPwd)));
						return Message("Ошибка при отправке e-mail сообщения. Для восстановления пароля обратитесь к администратору.");
					}
					
					MailQueue mailItem = template.GenerateMailQueue(user.Email, user.Login, new
					{
						login = user.Login,
						site = SiteUrl,
						url = recoverUrl
					});
					using (ITransaction t = Data.BeginTransaction())
					{
						Data.Save(mailItem);
						t.Commit();
					}
					
					// show message
					return Message("Проверьте ваш e-mail <b>" + model.Email + "</b>. На него было отправлено письмо с ключом подтверждения.");
				}
			}

			// error occured
			this.SaveModelState();
			return RedirectToAction("RecoverPwd");
		}

		/// <summary>
		/// Generates activation key and url for user. Saves key in user entity.
		/// </summary>
		/// <param name="user">User to generate for.</param>
		/// <returns>Activation url.</returns>
		protected Uri GenerateActivationKey(User user)
		{
			if (user == null)
				throw new ArgumentNullException("user");

			Guid guid = Guid.NewGuid();
			user.ActivationGuid = guid;
			return new Uri(SiteUrl, Url.Action("Activate", new { key = guid }));
		}

		/// <summary>
		/// Generates e-mail that he has registered.
		/// </summary>
		/// <param name="user">Registered user.</param>
		/// <param name="pwd">User password.</param>
		/// <param name="activationUrl">User activation url.</param>
		protected virtual MailQueue GenerateRegistrationEmail(User user, string pwd, Uri activationUrl = null)
		{
			// check
			if (user == null)
				throw new ArgumentNullException("user");
			if (!user.IsApproved && activationUrl == null)
				throw new ArgumentException("Activation url can't be null if user is not approved", "activationUrl");

			// generate e-mail
			string templateName = user.IsApproved ? UsersModule.MailTemplateRegister : UsersModule.MailTemplateRegisterApprove;
			MailTemplate template = Data.Query<MailTemplate>().Get(templateName, Module.Name, Language);
			if (template == null)
			{
				Log log = new Log(LogTypes.Warning, String.Format("Canot find mail template \"{0}\" to send e-mail message.", templateName), null);
				Data.Save(log);
				return null;
			}
			
			MailQueue mailItem = template.GenerateMailQueue(user.Email, user.Login, new
			{
				user.Login,
				pwd,
				url = activationUrl,
				site = SiteUrl
			});
			return mailItem;
		}
	}
}
